﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using Biosek.Formats.UniProt;
using Biosek;
using System.Diagnostics;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json;
using System.IO;

namespace FilterPotyvirusDataset
{
	public class Program
	{
		/// <summary>
		/// Toda la informacion de potyviridae en formato XML esta en
		/// http://www.uniprot.org/uniprot/?query=taxonomy%3apotyviridae&force=yes&format=xml
		/// </summary>
		/// <param name="args"></param>
		public static void Main(string[] args)
		{
			const string defaultInputFilename = "uniprot-taxonomy-potyviridae.xml";
			string inputFilename = args.FirstOrDefault();
			if (inputFilename == null) inputFilename = defaultInputFilename;

			if (!File.Exists(inputFilename))
			{
				Console.WriteLine("Archivo de datos no encontrado.");
				Console.WriteLine("Descargando toda la informacion desde UniProt (por favor espere...)");
				Console.WriteLine("Consulta taxonomy:potyviridae");
				using (var client = new WebClient())
				{
					client.DownloadFile("http://www.uniprot.org/uniprot/?query=taxonomy%3apotyviridae&force=yes&format=xml", inputFilename);
				}
				Console.WriteLine("Descarga finalizada");
			}

			Console.WriteLine("Cargando archivo Uniprot-XML");
			var allEntries = uniprot.LoadFromFile(inputFilename);

			Console.WriteLine("Procesando informacion...");
			var datasource = from mentry in allEntries.entry
											 let chains = mentry.GetChains().ToArray()
											 let sites = mentry.GetSites().ToArray()
											 let hasChains = chains.Count() > 0
											 let hasSites = sites.Count() > 0
											 // sites matching indica que la informacion en un descriptor 'site' y la de los descriptores 'chain'
											 // son congruentes y las asocia
											 let sitesMatching = from site in sites
																					 from chain in chains
																					 where
																						 site.GetFirstLocation() == chain.GetLastLocation() ||
																							chain.GetFirstLocation() == site.GetLastLocation()
																					 group chain by site
											 select new
											 {
												 entry = mentry,
												 chains,
												 sites,
												 hasChains,
												 hasSites,
												 hasSitesOnly = hasSites && !hasChains,
												 hasChainsOnly = hasChains && !hasSites,
												 hasChainsAndSites = hasChains && hasSites,
												 hasNoAnnotation = !hasChains && !hasSites,

												 sitesMatching,
												 // tiene algun match
												 hasMatches = sitesMatching.Any(),
												 // Todos los sitios del entry hicieron match
												 allSitesMatch = sites.All(site => sitesMatching.Any(matchingChains => matchingChains.Key == site)),
												 // cada sitio matched tiene exactamente dos chains asociadas
												 allMatchesHaveExactlyTwoChains = sitesMatching.All(matchingChains => matchingChains.Count() == 2),
												 // tiene exacamente 9 sitios matched
												 hasExactlyNineMatchingSites = sitesMatching.Count() == 9,
												 // tiene exactamente 10 chains
												 hasExactlyTenChains = chains.Count() == 10
											 };

			var pairsData = from mentry in datasource
											from c1 in mentry.chains
											from c2 in mentry.chains
											// sitio asociado a las dos cadenas (si lo hay)
											let sm = (from r in mentry.sitesMatching
																where r.Contains(c1) && r.Contains(c2)
																select r.Key).FirstOrDefault()
											// son dos cadenas consecutivas y ...
											where UniprotHelper.AreConsecutive(c1, c2) && (
													 (mentry.hasMatches && mentry.allSitesMatch && mentry.allMatchesHaveExactlyTwoChains && mentry.hasExactlyNineMatchingSites)
												|| (mentry.hasChainsOnly && mentry.hasExactlyTenChains)
												)
											let pair = PreparePair(mentry.entry, mentry.chains, mentry.sites, sm, c1, c2)
											group pair by pair.indexChain1 into grp_pairs
											select new
											{
												comment = grp_pairs.Key + " pairs ->  (mentry.hasMatches && mentry.allSitesMatch && mentry.allMatchesHaveExactlyTwoChains && mentry.hasExactlyNineMatchingSites) || (mentry.hasChainsOnly && mentry.hasExactlyTenChains)",
												count = grp_pairs.Count(),
												data = grp_pairs,
												index = grp_pairs.Key
											};
			foreach (var pairData in pairsData)
			{
				var pairdataFilename = "trustedPairs" + pairData.index + ".json";
				pairData.SaveJsonFile(pairdataFilename);
				Console.WriteLine("Saved {0} (count: {1})", pairdataFilename, pairData.count);
			}

			var allPairsData = from mentry in datasource
												 from c1 in mentry.chains
												 from c2 in mentry.chains
												 // sitio asociado a las dos cadenas (si lo hay)
												 let sm = (from r in mentry.sitesMatching
																	 where r.Contains(c1) && r.Contains(c2)
																	 select r.Key).FirstOrDefault()
												 // son dos cadenas consecutivas
												 where UniprotHelper.AreConsecutive(c1, c2)
												 select PreparePair(mentry.entry, mentry.chains, mentry.sites, sm, c1, c2);

			var allPairdataFilename = "allPairs.json";
			allPairsData.SaveJsonFile(allPairdataFilename);
			Console.WriteLine("Saved {0} (count: {1})", allPairdataFilename, allPairsData.Count());
		}

		public static SequenceSegmentPair PreparePair(entry entry, IList<featureType> allChains, IList<featureType> allSites, featureType site, featureType chain1, featureType chain2)
		{
			/* Ojo los valores de locaciones en el archivo Uniprot son indices INCLUSIVOS basados en 1.
			 * Las longitudes requieren ser calculadas con esto en mente.
			 */
			var obj = new SequenceSegmentPair()
			{
				accessions = entry.accession,
				lineage = entry.organism.lineage.LastOrDefault(),
				indexSite = allSites.IndexOf(site),
				indexChain1 = allChains.IndexOf(chain1),
				indexChain2 = allChains.IndexOf(chain2),
				descriptionChain1 = chain1.description,
				descriptionChain2 = chain2.description,
				chain1Start = chain1.GetFirstLocation() - 1,
				chain1End = chain1.GetLastLocation() - 1,
				chain2Start = chain2.GetFirstLocation() - 1,
				chain2End = chain2.GetLastLocation() - 1,
			};
			var chainLength = chain1.GetFeatureLength() + chain2.GetFeatureLength();
			//obj.sequence = entry.GetPartialSequence((int)obj.chain1Start, (int)chainLength);
			return obj;
		}


	}
}
